package com.sinsz.common;

import com.alipay.sofa.rpc.common.RpcConstants;
import com.alipay.sofa.rpc.config.*;
import com.alipay.sofa.rpc.context.RpcRuntimeContext;
import com.alipay.sofa.rpc.hystrix.HystrixConstants;
import com.alipay.sofa.rpc.hystrix.SofaHystrixConfig;
import com.sinsz.common.annotation.EnableBolt;
import com.sinsz.common.bolt.BoltSetterFactory;
import com.sinsz.common.filter.SinszRequestFilter;
import com.sinsz.common.interceptor.LogsInterceptor;
import com.sinsz.common.properties.CommonProperties;
import com.sinsz.common.util.ValidateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 配置入口
 * @author chenjianbo
 */
@Configuration
@ComponentScans({
        @ComponentScan({"com.sinsz.common"})
})
@ServletComponentScan(basePackageClasses = {SinszRequestFilter.class})
@EnableConfigurationProperties(CommonProperties.class)
public class CommonAutoConfiguration implements WebMvcConfigurer {

    private final CommonProperties properties;

    @Autowired
    public CommonAutoConfiguration(CommonProperties properties) {
        this.properties = properties;
    }

    @Bean
    public HttpHeaders httpHeaders() {
        HttpHeaders headers = new HttpHeaders();
        if (properties.getHeaders() != null
                && !properties.getHeaders().isEmpty()) {
            properties.getHeaders().forEach(headers::set);
        }
        return headers;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogsInterceptor()).addPathPatterns("/**");
    }

    /**
     * Bolt服务配置
     */
    @EnableBolt
    @Configuration
    public static class BoltAutoConfiguration implements ApplicationRunner,DisposableBean, Ordered {

        private final CommonProperties commonProperties;

        private static Logger logger = LoggerFactory.getLogger(BoltAutoConfiguration.class);

        @Autowired(required = false)
        public BoltAutoConfiguration(CommonProperties commonProperties) {
            this.commonProperties = commonProperties;
        }

        private int getConnectTimeout() {
            Integer timeout = commonProperties.getBolt().getConnectTimeout();
            return timeout == null || timeout < 5000 ? 5000 : timeout;
        }

        private int getTimeout() {
            Integer timeout = commonProperties.getBolt().getTimeout();
            return timeout == null || timeout < 3000 ? 3000 : timeout;
        }

        private int getExecutionTimeout() {
            Integer timeout = commonProperties.getBolt().getHystrixTimeout();
            return timeout == null || timeout < 6000 ? 6000 : timeout;
        }

        @Bean
        public ApplicationConfig boltApplication() {
            ApplicationConfig config = new ApplicationConfig();
            config.setAppName(commonProperties.getBolt().getAppName());
            return config;
        }

        @Bean
        public RegistryConfig boltRegistry() {
            RegistryConfig registry = new RegistryConfig();
            registry.setProtocol(RpcConstants.REGISTRY_PROTOCOL_ZK);
            registry.setAddress(commonProperties.getBolt().getZkCluster());
            registry.setConnectTimeout(getConnectTimeout());
            registry.setTimeout(getTimeout());
            return registry;
        }

        @Bean
        public ServerConfig boltServer() {
            ServerConfig server = new ServerConfig();
            server.setPort(commonProperties.getBolt().getPort());
            server.setProtocol(RpcConstants.PROTOCOL_TYPE_BOLT);
            server.setDaemon(false);
            server.setBoundHost(
                    ValidateUtils.isIP(commonProperties.getBolt().getBoundHost()) ? commonProperties.getBolt().getBoundHost()
                            : "127.0.0.1"
            );
            return server;
        }

        private <T> void provider(Class<T> clz, T ref) {
            ProviderConfig<T> provider = new ProviderConfig<>();
            provider.setInterfaceId(clz.getName());
            provider.setRef(ref);
            provider.setTimeout(getTimeout());
            provider.setApplication(boltApplication());
            provider.setRegistry(boltRegistry());
            provider.setServer(boltServer());
            provider.export();
        }

        private <T> ConsumerConfig<T> consumer(Class<T> clz, T failback) {
            ConsumerConfig<T> consumer = new ConsumerConfig<>();
            consumer.setApplication(boltApplication());
            consumer.setRegistry(boltRegistry());
            consumer.setInterfaceId(clz.getName());
            consumer.setRetries(commonProperties.getBolt().getRetries());
            consumer.setTimeout(getTimeout());
            consumer.setConnectTimeout(getConnectTimeout());
            consumer.setParameter(HystrixConstants.SOFA_HYSTRIX_ENABLED, String.valueOf(true));
            consumer.setProtocol(RpcConstants.PROTOCOL_TYPE_BOLT);
            SofaHystrixConfig.registerFallback(consumer, failback);
            SofaHystrixConfig.registerSetterFactory(consumer, new BoltSetterFactory(getExecutionTimeout()));
            return consumer;
        }

        @Override
        public int getOrder() {
            return Integer.MAX_VALUE;
        }

        @Override
        public void run(ApplicationArguments args) {
            BoltUtils.getInstance().getProviderBeans().forEach(action -> provider(action.getClz(), action.getRef()));
            BoltUtils.getInstance().getReferenceBeans().forEach(action -> BoltUtils.getInstance().putBean(
                    action.getClz(),
                    consumer(action.getClz(), action.getFailback())
            ));
            logger.info("Rpc Service Loading Completed!");
        }

        @Override
        public void destroy() {
            RpcRuntimeContext.destroy();
        }

    }

}